package com.nulldev.util.web.HttpServer3.extensions.nCaptcha.captchas.v4.proposal_d;

import java.io.IOException;

import com.nulldev.util.VariableAPI.StringsUtil;
import com.nulldev.util.languages.json.uniapi.UniJsonValue;
import com.nulldev.util.web.HttpServer3.extensions.nCaptcha.backend.user.User;
import com.nulldev.util.web.HttpServer3.extensions.nCaptcha.backend.user.UserInfoType;
import com.nulldev.util.web.HttpServer3.extensions.nCaptcha.backend.user.UserManager;
import com.nulldev.util.web.HttpServer3.extensions.websocket.Peer;
import com.nulldev.util.web.HttpServer3.extensions.websocket.WebSocketPage;
import com.nulldev.util.web.HttpServer3.extensions.websocket.util.CloseReason.CloseCodes;

public class GyroscopeChallengeWebSocketPage extends WebSocketPage {

	private static final float ALLOWED_POSITIONAL_ERROR = 0.09f;

	public GyroscopeChallengeWebSocketPage() {
		super("/captcha/gyro");
	}

	@Override
	public void onTextMessage(final Peer peer, final String message) {
		if (StringsUtil.isBlank(message)) {
			/* Reject all INVALID messages */
			try {
				peer.sendClose(CloseCodes.VIOLATED_POLICY);
			} catch (IOException e) {
			}
		} else if (!UniJsonValue.probablyJSON(message)) {
			/* Reject all non-JSON messages */
			try {
				peer.sendClose(CloseCodes.VIOLATED_POLICY);
			} catch (IOException e) {
			}
		} else {
			final UniJsonValue data = UniJsonValue.parse(message);
			if (!__is_valid_message(data)) {
				/* Not a valid JSON object. */
				try {
					peer.sendClose(CloseCodes.CANNOT_ACCEPT);
				} catch (IOException e) {
				}
			} else {
				final float posX = data.getFloat("x");
				final float posY = data.getFloat("y");
				final float posZ = data.getFloat("z");
				final String uuid = data.getString("uuid");
				final User user = UserManager.loadUser(uuid);
				if (user == null || user.captchaDone()) {
					try {
						peer.sendClose(CloseCodes.VIOLATED_POLICY);
					} catch (IOException e) {
					}
				} else {
					final float expectedPosX = (float) user.getUserInfoObject(UserInfoType.USER_INFO_V4_GRYO_CHALLENGE_POS_X);
					final float expectedPosY = (float) user.getUserInfoObject(UserInfoType.USER_INFO_V4_GRYO_CHALLENGE_POS_Y);
					final float expectedPosZ = (float) user.getUserInfoObject(UserInfoType.USER_INFO_V4_GRYO_CHALLENGE_POS_Z);
					/*
					 * TODO: Should we check for sudden jumps in positioning to avoid brute force?
					 */
					final float distanceFromPosX = Math.abs(expectedPosX - posX);
					final float distanceFromPosY = Math.abs(expectedPosY - posY);
					final float distanceFromPosZ = Math.abs(expectedPosZ - posZ);
					if (isWithinError(distanceFromPosX, distanceFromPosY, distanceFromPosZ)) {
						final UniJsonValue msg = UniJsonValue.object(1);
						msg.add("ok", true);
						user.captchaPassed();
						user.removeInfoObject(UserInfoType.USER_INFO_V4_GRYO_CHALLENGE_POS_X);
						user.removeInfoObject(UserInfoType.USER_INFO_V4_GRYO_CHALLENGE_POS_Y);
						user.removeInfoObject(UserInfoType.USER_INFO_V4_GRYO_CHALLENGE_POS_Z);
						try {
							peer.write(msg.toString());
						} catch (IOException e) {
						}
					} else {
						final UniJsonValue msg = UniJsonValue.object(2);
						msg.add("ok", false);
						final float bar = ((distanceFromPosX * 100) + (distanceFromPosY * 100) + (distanceFromPosZ * 100)) / 3;
						msg.add("bar", bar);
						try {
							peer.write(msg.toString());
						} catch (IOException e) {
						}
					}
				}
			}
		}
	}

	private boolean isWithinError(final float distFromX, final float distFromY, final float distFromZ) {
		return distFromX <= ALLOWED_POSITIONAL_ERROR && distFromY <= ALLOWED_POSITIONAL_ERROR && distFromZ <= ALLOWED_POSITIONAL_ERROR;
	}

	private boolean __is_valid_message(final UniJsonValue object) {
		return object.contains("x") && object.contains("y") && object.contains("z") && object.contains("uuid");
	}

	@Override
	public void onBinaryMessage(final Peer peer, final byte[] message) {
		/* Reject all BINARY messages */
		try {
			peer.sendClose(CloseCodes.VIOLATED_POLICY);
		} catch (IOException e) {
		}
	}

	@Override
	public void peerConnected(final Peer peer) {
		/* NO-OP */
	}

	@Override
	public void peerDisconnected(final Peer peer) {
		/* NO-OP */
	}

}
